Skip to content

feat(conversation): let plugin rows manage messages without visiting a channel - #2512

Merged
kazemek21 merged 3 commits into
block:mainfrom
kazemek21:kylez_NEXUS-4193_no-visit-conversation-scope
Nov 5, 2026
Merged

kazemek21 merged 3 commits into
block:mainfrom
kazemek21:kylez_NEXUS-4193_no-visit-conversation-scope

Conversation

@kazemek21

@kazemek21 kazemek21 commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

Adds conversation.ui.Conversation, a public host-owned scope that lets a plugin's own ui.Message rows and ui.Composer for one channel get native Edit, confirmed Delete with recovery and retry, and host media review. Mounting it never marks anything read, follows a thread or enters the channel visit. Cross-channel overview cards need this; today a bare ui.Message row has neither Edit nor Delete, and ui.Thread always visits.

Fixes NEXUS-4193

How it works

ui.Conversation({ session, scope, channelId, channelName?, threadId?, children(interactions) }) renders the existing ConversationChannel in its host-dialog, non-visiting mode (host plus active={false}). Inbox Drafts already uses this mode internally. Inside it:

  • MessageManagement supplies the row menu's Edit/Delete under the existing permission checks, the delete confirmation and the failed/unconfirmed recovery notice.
  • MessageEditScope makes Edit target the ui.Composer in the same scope. A scope without a composer offers no Edit, as in other previews.
  • ConversationHost owns media review and panel overlays, focus return, and the existing stale-session guard (nothing opens once the relay snapshot is no longer this ready session).

children receives the host's onOpenLink, canOpenLink and onOpenMediaReview to pass to the rows. EmbeddedThread and the new scope now share one HostedChannel wrapper, so ui.Thread behavior is unchanged. No changes to MessageManagement, ConversationHost, MessageComposer or contracts.ts.

Evidence

src/features/conversation/EmbeddedConversation.test.tsx mounts two fictional channel cards (real relay session, real ui.Message rows and ui.Composer from ConversationService; only MediaReviewViewer is reduced, since it has its own suites). It covers:

  • the same rows without the scope have no Edit/Delete; inside it, each card offers both
  • mount, background presentation, re-present and unmount never call enterChannel, leaveChannel, follow, markMessageRead, markChannelRead or the roster's profiles.reserve, and publish nothing
  • Edit fills only that card's composer and publishes a kind-40003 edit tagged with that card's channel. The other card's message is untouched.
  • Delete: cancel publishes nothing; confirm publishes kind 5; a relay rejection shows the recovery notice in that card only; Retry resends the same event id and the row goes away
  • media review opens with the card's channel and message, and stops opening after the session stops being ready

Fail-then-pass: with active={false} removed from the scope, all 4 tests fail (enterChannel and profiles.reserve are called). The scope restored, all pass.

The packed @buzz/author consumer fixture (tests/fixtures/conversation-consumer) now renders its rows and composer inside ui.Conversation and passes interactions.onOpenLink to its rows. So the generated declarations must export the new component and its children(interactions) type, and the existing conversation.spec.mjs journey mounts it in the real app. The fixture type-checks against freshly built declarations. As a negative control, a misspelled interactions field fails tsc.

Run on a Linux Blox box on base a34ba4f (the local host-build guard refused under load):

  • vitest run src/features/conversation src/features/messages/MessageManagement.test.tsx src/bundled/inbox/DraftsView.test.tsx: 19 files, 221 tests passed
  • tsc --noEmit and biome check --error-on-warnings on the touched files: clean
  • pnpm author:build, packed and installed into a copy of the consumer fixture, then pnpm build (tsc): clean

No browser journeys were added or removed. The existing consumer journey now mounts the scope; it runs only in hosted CI, not locally. Focus, overlay and native-edit geometry are unchanged existing ConversationHost/composer behavior that existing suites already cover.

Limits and coordination

Initiative context

Buzz Threads status

🤖 Generated with Claude Code

kazemek21 and others added 3 commits November 4, 2026 14:38
…a channel

Add conversation.ui.Conversation, a host-owned scope for a page's own
ui.Message rows and ui.Composer in one channel. It reuses the existing
non-visiting ConversationChannel host mode, so rows get native edit,
confirmed delete with recovery and retry, a per-scope edit target and
host media review, while mounting never marks read, follows or holds the
channel roster.

Fixes NEXUS-4193

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kyle Zemek <kylez@squareup.com>
… consumer

Observe the profile reservation an open channel makes, exercise
ui.Conversation and its interactions in the packed author consumer
fixture, and name the instance rather than the scope prop in the docs.

Refs NEXUS-4193

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kyle Zemek <kylez@squareup.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kyle Zemek <kylez@squareup.com>
@kazemek21
kazemek21 marked this pull request as ready for review November 4, 2026 23:39
@kazemek21
kazemek21 requested a review from a team as a code owner November 4, 2026 23:39

@joahg joahg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Read the EmbeddedConversation/HostedChannel refactor and the no-visit scope. EmbeddedThread behavior is unchanged, and the new tests assert enterChannel, follow and mark-read are never called.

@kazemek21
kazemek21 merged commit 520dc16 into block:main Nov 5, 2026
41 checks passed
cheng-block added a commit to cheng-block/buzz-app that referenced this pull request Nov 5, 2026
…ettings-shift-esc

* upstream/main: (40 commits)
  feat(conversation): let plugin rows manage messages without visiting a channel (block#2512)
  feat(github): make side-panel link opening optional (block#2629)
  feat(messages): show a blurred thumbnail until an image loads (block#2556)
  fix(relay): give large roster and read-state reads a 30s deadline (block#2627)
  fix(user-status): show a status emoji's :name: on hover where the full status shows (block#2384)
  fix(inbox): keep the list's place when returning in the same session (block#2134)
  fix(messages): pass verified labels to the forward recipient picker (block#2633)
  feat(messages): forward a message to a person (block#2167)
  docs(agents): design relay-facing paths for the whole fleet at once (block#2620)
  Route debug sessions through the credential helper (block#2541)
  perf(search): skip no-op slow-search resets on each keystroke (block#2605)
  perf(agents): dedupe channel members only for dm connection setup (block#2603)
  perf(search): keep the search tab row across palette keystrokes (block#2588)
  perf(sidebar): skip the hidden actions menu in solo and team headers (block#2582)
  perf(search): rank each palette name once per render (block#2606)
  perf(sidebar): skip re-rendering unchanged animated channel rows (block#2572)
  feat(settings): follow the system clock and let people hide message times (block#2440)
  feat(plugins): repository access rules for Git imports (block#2484)
  feat(desktop): add Settings to the macOS app menu (block#2594)
  Preserve and renew external app approval (block#2464)
  ...

Signed-off-by: Cheng Peng <cheng@block.xyz>
Co-authored-by: Cursor <cursoragent@cursor.com>
johnmatthewtennant added a commit that referenced this pull request Nov 5, 2026
…thumbnails

* origin/main: (120 commits)
  feat(conversation): let plugin rows manage messages without visiting a channel (#2512)
  feat(github): make side-panel link opening optional (#2629)
  feat(messages): show a blurred thumbnail until an image loads (#2556)
  fix(relay): give large roster and read-state reads a 30s deadline (#2627)
  fix(user-status): show a status emoji's :name: on hover where the full status shows (#2384)
  fix(inbox): keep the list's place when returning in the same session (#2134)
  fix(messages): pass verified labels to the forward recipient picker (#2633)
  feat(messages): forward a message to a person (#2167)
  docs(agents): design relay-facing paths for the whole fleet at once (#2620)
  Route debug sessions through the credential helper (#2541)
  perf(search): skip no-op slow-search resets on each keystroke (#2605)
  perf(agents): dedupe channel members only for dm connection setup (#2603)
  perf(search): keep the search tab row across palette keystrokes (#2588)
  perf(sidebar): skip the hidden actions menu in solo and team headers (#2582)
  perf(search): rank each palette name once per render (#2606)
  perf(sidebar): skip re-rendering unchanged animated channel rows (#2572)
  feat(settings): follow the system clock and let people hide message times (#2440)
  feat(plugins): repository access rules for Git imports (#2484)
  feat(desktop): add Settings to the macOS app menu (#2594)
  Preserve and renew external app approval (#2464)
  ...

Signed-off-by: John Tennant <jtennant@squareup.com>

# Conflicts:
#	src-tauri/src/relay.rs
agude-sq added a commit to agude-sq/buzz-app that referenced this pull request Nov 5, 2026
* origin/main: (28 commits)
  fix(mcp): accept create scopes in durable approvals (block#2650)
  feat(bestie): Bestie tab and Remembers you for a hosted Bestie (block#2478)
  fix(relay): p-tag a one-to-one DM's agent partner on new messages (block#2475)
  fix(agents): show each message author's username and display name (block#2628)
  fix(agents): preserve visible edits in conversation history (block#2355)
  fix(canvas): keep todo edits from approving unrelated instructions (block#2350)
  fix(messages): keep messages in their canonical channel (block#2349)
  NEXUS-4165: Add Thinking to agent creation (block#2409)
  feat(plugins): let a plugin paint a backdrop behind the shell (block#2437)
  feat(search): lead empty quick open with unread conversations (block#2634)
  NEXUS-4170: Allow external apps to create private Solo chats (block#2411)
  fix(acp): count grouped thinking levels when checking a synced level (block#2640)
  feat(conversation): let plugin rows manage messages without visiting a channel (block#2512)
  feat(github): make side-panel link opening optional (block#2629)
  feat(messages): show a blurred thumbnail until an image loads (block#2556)
  fix(relay): give large roster and read-state reads a 30s deadline (block#2627)
  fix(user-status): show a status emoji's :name: on hover where the full status shows (block#2384)
  fix(inbox): keep the list's place when returning in the same session (block#2134)
  fix(messages): pass verified labels to the forward recipient picker (block#2633)
  feat(messages): forward a message to a person (block#2167)
  ...

# Conflicts:
#	tests/browser/settings.spec.mjs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants